//---------------[CSE420]----------[Project #4]----------[Part #3]---------------
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libspe2.h>
#include <cbe_mfc.h>
#include <pthread.h>
#include <unistd.h>
#include <math.h>
#include "common.h"


//------------------------------[Data Structures]--------------------------------
/* Data structure for running SPE thread */
typedef struct ppu_pthread_data
{
	spe_context_ptr_t   speid;
	pthread_t           pthread;
	uint64_t           *argp;
} ppu_pthread_data_t;


//------------------------------[Global Variables]-------------------------------
int matrixA_InPPE[NUM_OF_ROWS][NUM_OF_COLS]  QWORD_ALIGNED;
int matrixB_InPPE[NUM_OF_ROWS][NUM_OF_COLS]  QWORD_ALIGNED;
int matrixC_InPPE[NUM_OF_ROWS][NUM_OF_COLS]  QWORD_ALIGNED;
int result                                   QWORD_ALIGNED;

extern spe_program_handle_t spu;
CONTROL_BLOCK cb QWORD_ALIGNED;
ppu_pthread_data_t data;


//----------------------------[Forward Declarations]-----------------------------
void *ppu_pthread_function(void *arg);
void initMatrices();
void transposeMatrixB();
void printMatrix(char *name, int *matrix[][NUM_OF_COLS]);


//------------------------------------[Main]-------------------------------------
int main(int argc, char *argv[])
{
	int rc;
	initMatrices();
	//printMatrix("A", &matrixA_InPPE);
	//printMatrix("B", &matrixB_InPPE);

	/* I transpose B so a row in matrixB_Transpose = a column in matrixB,
	 * this makes the pass much more simplier and should allow a block read
	 * from memory, instead of jumping NUM_OF_COLUMNS away each step to
	 * extract the colum from matrixB.
	 */
	transposeMatrixB();

/********************************************************/
#ifdef TIMING
long unsigned int start_t, end_t;
float total_t = 0;
#endif
/********************************************************/

	for(int row = 0; row < NUM_OF_ROWS; row++)
	{
		for(int col = 0; col < NUM_OF_COLS; col++)
		{
			/* Fill in control block */
			cb.matrixA_row = &matrixA_InPPE[row][ 0 ];
			cb.matrixB_col = &matrixB_InPPE[col][ 0 ];//[ 0 ][col];
			cb.SPU_result  = &result;

			/* Create SPE context and load SPE program into the SPE context */
			if ((data.speid = spe_context_create (0, NULL)) == NULL)
			{
				fprintf(stderr, "Failed spe_context_create(errno=%d strerror=%s)\n", errno, strerror(errno));
				exit (3);
			}
  
			/* Load program. */
			if ((rc = spe_program_load (data.speid, &spu)) != 0)
			{
				fprintf (stderr, "Failed spe_program_load(errno=%d strerror=%s)\n", errno, strerror(errno));
				exit (3);
			}
  

			/* Pass Control Block address to SPU. */
			data.argp = (uint64_t *) &cb;

/********************************************************/
#ifdef TIMING
start_t = __mftb();
#endif
/********************************************************/

			/* Create SPE pthreads */
			if ((rc = pthread_create (&data.pthread, NULL, &ppu_pthread_function, &data)) != 0)
			{
				fprintf (stderr, "Failed pthread_create(errno=%d strerror=%s)\n", errno, strerror(errno));
				exit (3);
			}
  
			/* Wait for SPE to complete */
			if ((rc = pthread_join (data.pthread, NULL)) != 0)
			{
				fprintf (stderr, "Failed pthread_join(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
				exit (1);
			}

			/* Grab result */
			matrixC_InPPE[row][col] = result;

/********************************************************/
#ifdef TIMING
end_t = __mftb();
total_t += (float)((end_t-start_t)/79.8);
#endif
/********************************************************/

			/* Destroy the SPE contexts */
			if ((rc = spe_context_destroy (data.speid)) != 0)
			{
				fprintf (stderr, "Failed spe_context_destroy(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
				exit (1);
			}

		}//end column for loop.
	}//end row for loop.

/********************************************************/
#ifdef TIMING
printf("\n\n\nTotal time used by SPE: %f us\n\n\n", total_t);
#endif
/********************************************************/

	printMatrix("C", matrixC_InPPE);
	return 0;
}


//---------------------------------[Functions]-----------------------------------
/* Create and run one SPE thread */
void *ppu_pthread_function(void *arg)
{
	ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
	int rc;
	unsigned int entry = SPE_DEFAULT_ENTRY;
	if ((rc = spe_context_run(datap->speid, &entry, 0, datap->argp, NULL, NULL)) < 0)
	{
		fprintf(stderr, "Failed spe_context_run(rc=%d, errno=%d, strerror=%s)\n", rc, errno, strerror(errno));
		exit(1);
	}
	pthread_exit(NULL);
}

/* Initializes A and B with random integers from (0, 100] */
void initMatrices()
{
	for(int row = 0; row < NUM_OF_ROWS; row++)
	{
		for(int col = 0; col < NUM_OF_COLS; col++)
		{
			matrixA_InPPE[row][col] = random() % 100;
			matrixB_InPPE[row][col] = random() % 100;
		}
	}

}

/* Transposes matrixB_InPPE */
void transposeMatrixB()
{
	int temp;

	for(int row = 0; row < NUM_OF_ROWS; row++)
	{
		for(int col = row + 1; col < NUM_OF_COLS; col++)
		{
			temp                    = matrixB_InPPE[row][col];
			matrixB_InPPE[row][col] = matrixB_InPPE[col][row];
			matrixB_InPPE[col][row] = temp;
		}
	}
}

/* Prints a matrix */
void printMatrix(char *name, int *matrix[][NUM_OF_COLS])
{

	printf("Matrix %s:\n", name);
	for(int row = 0; row < NUM_OF_ROWS; row++)
	{
		for(int col = 0; col < NUM_OF_COLS; col++)
		{
			printf("%8d", matrix[row][col]);
		}
		printf("\n");
	}
}
//------------------------------------[END]--------------------------------------
